package be.bejug.scrumadmin.spring;

import java.io.IOException;
import java.util.Locale;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;

import com.vaadin.Application;
import com.vaadin.terminal.gwt.server.AbstractApplicationServlet;

/**
 * This Vaadin application servlet looks up the {@link Application} instance
 * from the {@link WebApplicationContext} returned by
 * {@link WebApplicationContextUtils#getWebApplicationContext(javax.servlet.ServletContext)}
 * . The name of the application bean has to be defined as a servlet parameter
 * named <code>applicationBean</code>. In addition, the application bean has to
 * have either prototype or session scope, otherwise all users will share the
 * same application instance (not good).
 * <p>
 * This servlet also resolves the locale using a Spring locale resolver and
 * updates the requests accordingly.
 * <p>
 * This class is based on the <a
 * href="http://dev.vaadin.com/browser/incubator/SpringApplication"
 * >SpringApplication example</a> by Petri Hakala, with some minor modifications
 * here and there by yours truly.
 * 
 * @author Glenn Dejaeger
 */
public class SpringApplicationServlet extends AbstractApplicationServlet {

	private static final long serialVersionUID = 9119643883315827366L;

	/**
	 * Protected Apache Commons Log for logging stuff.
	 */
	protected transient final Log logger = LogFactory.getLog(getClass());

	private transient WebApplicationContext applicationContext;

	private Class<? extends Application> applicationClass;

	private String applicationBean;

	private transient LocaleResolver localeResolver;

	@SuppressWarnings("unchecked")
	@Override
	public void init(final ServletConfig servletConfig) throws ServletException {
		super.init(servletConfig);
		/*
		 * Look up the name of the Vaadin application bean. The bean should be
		 * either a prototype or have session scope.
		 */
		applicationBean = servletConfig.getInitParameter("applicationBean");
		if (applicationBean == null) {
			if (logger.isErrorEnabled()) {
				logger.error("ApplicationBean not specified in servlet parameters");
			}
			throw new ServletException(
					"ApplicationBean not specified in servlet parameters");
		}

		if (logger.isInfoEnabled()) {
			logger.info("Using applicationBean '" + applicationBean + "'");
		}

		/*
		 * Fetch the Spring web application context
		 */
		applicationContext = WebApplicationContextUtils
				.getWebApplicationContext(servletConfig.getServletContext());

		if (applicationContext.isSingleton(applicationBean)) {
			if (logger.isErrorEnabled()) {
				logger.error("ApplicationBean must not be a singleton");
			}
			throw new ServletException(
					"ApplicationBean must not be a singleton");
		}
		if (!applicationContext.isPrototype(applicationBean)
				&& logger.isWarnEnabled()) {
			logger.warn("ApplicationBean is not a prototype");
		}

		/*
		 * Get the application class from the application context
		 */
		applicationClass = (Class<? extends Application>) applicationContext
				.getType(applicationBean);
		if (logger.isDebugEnabled()) {
			logger.debug("Vaadin application class is [" + applicationClass
					+ "]");
		}
		/*
		 * Initialize the locale resolver
		 */
		initLocaleResolver(applicationContext);
	}

	private void initLocaleResolver(final ApplicationContext context) {
		try {
			/*
			 * Try to look up the locale resolver from the application context
			 */
			localeResolver = context.getBean(
					DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME,
					LocaleResolver.class);
			if (logger.isInfoEnabled()) {
				logger.info("Using LocaleResolver [" + localeResolver + "]");
			}
		} catch (NoSuchBeanDefinitionException e) {
			/*
			 * No locale resolver was defined in the application context, so we
			 * create a default one.
			 */
			localeResolver = new SessionLocaleResolver();
			if (logger.isWarnEnabled()) {
				logger.warn("Unable to locate LocaleResolver with name '"
						+ DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME
						+ "', using default [" + localeResolver + "]");
			}
		}
	}

	@Override
	protected void service(final HttpServletRequest request,
			final HttpServletResponse response) throws ServletException,
			IOException {
		/*
		 * Resolve the locale from the request
		 */
		final Locale locale = localeResolver.resolveLocale(request);
		if (logger.isDebugEnabled()) {
			logger.debug("Resolved locale [" + locale + "]");
		}

		/*
		 * Store the locale in the LocaleContextHolder, making it available to
		 * Spring.
		 */
		LocaleContextHolder.setLocale(locale);
		ServletRequestAttributes requestAttributes = new ServletRequestAttributes(
				request);
		RequestContextHolder.setRequestAttributes(requestAttributes);
		try {
			/*
			 * We need to override the request to return the locale resolved by
			 * Spring.
			 */
			super.service(new HttpServletRequestWrapper(request) {
				@Override
				public Locale getLocale() {
					return locale;
				}
			}, response);
		} finally {
			if (!locale.equals(LocaleContextHolder.getLocale())) {
				/*
				 * The locale in LocaleContextHolder was changed during the
				 * request, so we have to update the resolver.
				 */
				if (logger.isDebugEnabled()) {
					logger.debug("Locale changed, updating locale resolver");
				}
				localeResolver.setLocale(request, response,
						LocaleContextHolder.getLocale());
			}
			LocaleContextHolder.resetLocaleContext();
			RequestContextHolder.resetRequestAttributes();
		}
	}

	@Override
	protected Class<? extends Application> getApplicationClass()
			throws ClassNotFoundException {
		return applicationClass;
	}

	@Override
	protected Application getNewApplication(final HttpServletRequest request)
			throws ServletException {
		/*
		 * AS the application bean should be defined either as a prototype or a
		 * sessions scoped bean, this call should always return a new
		 * application instance.
		 */
		return (Application) applicationContext.getBean(applicationBean);
	}

}
